(function(){

    function formStack(depth) {
        if (!depth)
            depth = 0;
        var retval = "\n";
        var stack = Components.stack.caller.caller;
        while (stack && depth > 0)
        {
            stack = stack.caller;
            depth--
        }
        while (stack) {
            retval += "@" + stack.filename + ":" + stack.lineNumber + "\n";
            stack = stack.caller;
        }
        return retval;
    }

    function parseStack(fullstackstr,depth) {
        if (!depth)
            depth = 0;
        var stackarr = fullstackstr.split("\n");
        var retval = null;
        for (var ii = stackarr.length - 2; ii >= 0; ii--)
        {
            var stackstr = stackarr[ii];
            var atIdx = stackstr.lastIndexOf("@");
            if (atIdx < 0)
                break;
            var colIdx = stackstr.lastIndexOf(":");
            if (colIdx < 0)
                break;
            var locUrl = stackstr.substring(atIdx + 1,colIdx);
            if (!locUrl)
                break;
            var lineNum = parseInt(stackstr.substring(colIdx + 1),10);
            retval = { filename: locUrl, lineNumber: lineNum, caller: retval };
        }
        for (ii = 0; ii < depth; ii++)
        {
            retval = retval.caller;
        }
        return retval;
    }

    lucidapi.l10n = function()
    {
        var retval = "LucidError";
        try {
            retval = arguments[0];
            var args = Array.prototype.slice.call(arguments,1);
            if (args.length == 1 && typeof(args[0].splice) == "function")
                args = args[0];
            if (args)
            {
                retval = lucidapi.i18n.getFormattedString(retval,args);
            }
            else
            {
                retval = lucidapi.i18n.getString(retval);
            }
        } catch (e) {
            Components.utils.reportError(e);
            if (e.stack)
                Components.utils.reportError(e.stack);
        }
        return retval;
    }

    lucidapi.createThrowable = function()
    {
        var retval = new Error(lucidapi.l10n.apply(null,arguments));
        retval.stackdepth = 1;
        return retval;
    }

    lucidapi.logimpl = function(level,loggable)
    {
        var msg = loggable.message;
        var stack = loggable.stack;
        var pstack = parseStack(stack?stack:formStack(1),loggable.stackdepth);
        var cs = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
        if (level < 2)
        {
            var se = Components.classes["@mozilla.org/scripterror;1"].createInstance(Components.interfaces.nsIScriptError);
            se.init("Lucidity: " + msg,
                pstack.filename, null,  pstack.lineNumber,
                0, level, "aCategory");
            cs.logMessage(se);
        }
        else
        {
            cs.logStringMessage("Lucidity: " + msg);
        }
        if (!Components.classes["@mozilla.org/thread-manager;1"].getService().isMainThread)
            return;
        var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
        observerService.notifyObservers(null,"lucid-logmsg",JSON.toString({message:msg,level:level,stack:pstack}));
    }

})();

(function(){

    const Ci = Components.interfaces;

    lucidapi.getXpcomService = function( classid, ifaceid )
    {
        return Components.classes[classid].getService(Components.interfaces[ifaceid]);
    }

    const IS_WINDOWS = (lucidapi.getXpcomService("@mozilla.org/xre/app-info;1", "nsIXULRuntime").OS == "WINNT");

    function lucid_getVersion()
    {
        var em = lucidapi.getXpcomService("@mozilla.org/extensions/manager;1", "nsIExtensionManager");
        return em.getItemForID("lucidity@emergent.org").version;
    }

    const LUCID_VERSION = lucid_getVersion();

    const NSIFileOutputStream = Components.Constructor("@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream");

    const NSILocalFile = Components.Constructor("@mozilla.org/file/local;1", "nsILocalFile","initWithPath");
    const NSIConverterOutputStream = Components.Constructor("@mozilla.org/intl/converter-output-stream;1", "nsIConverterOutputStream");
    const NSIConverterInputStream = Components.Constructor("@mozilla.org/intl/converter-input-stream;1", "nsIConverterInputStream");
    const NSIFileInputStream = Components.Constructor("@mozilla.org/network/file-input-stream;1", "nsIFileInputStream");
    const NSIBinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
    const NSIBinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream");
    const NSIFilePicker = Components.Constructor("@mozilla.org/filepicker;1", "nsIFilePicker");
    const NSIXMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1","nsIXMLHttpRequest");

    function lucid_getNativeStringBundle(src)
    {
        var localeService = lucidapi.getXpcomService("@mozilla.org/intl/nslocaleservice;1","nsILocaleService");
        var appLocale =  localeService.getApplicationLocale();
        var stringBundleService = lucidapi.getXpcomService("@mozilla.org/intl/stringbundle;1","nsIStringBundleService");
        return stringBundleService.createBundle(src, appLocale);
    }

    lucidapi.__defineGetter__("i18n", function() {
        var nativebundle = lucid_getNativeStringBundle("chrome://lucidity/locale/lucidity.properties");
        return {
            getString: function(key)
            {
                return nativebundle.GetStringFromName(key);
            },
            getFormattedString: function(key,parms)
            {
                if (!parms)
                    parms = [];
                return nativebundle.formatStringFromName(key,parms,parms.length);
            }
        };
    });
    
    var lucidint = {};

    var sm_passCache = {
        entries: {}
    };

    lucidapi.isPassphraseCacheEmpty = function()
    {
        var key;
        //noinspection LoopStatementThatDoesntLoopJS
        for (key in sm_passCache.entries)
        {
            return false;
        }
        return true;
    }

    lucidapi.config = {

        getLucidPrefBranch: function()
        {
            var prefs = lucidapi.getXpcomService("@mozilla.org/preferences-service;1","nsIPrefService");
            return prefs.getBranch("extensions.lucidity.");
        },

        getLucidPref: function( key )
        {
            try
            {
                var prefs = this.getLucidPrefBranch();
                var btype = prefs.getPrefType( key );
                switch (btype) {
                    case Ci.nsIPrefBranch.PREF_STRING:
                        return prefs.getCharPref(key);
                        break;
                    case Ci.nsIPrefBranch.PREF_INT:
                        return prefs.getIntPref(key);
                        break;
                    case Ci.nsIPrefBranch.PREF_BOOL:
                        return prefs.getBoolPref(key);
                        break;
                    case Ci.nsIPrefBranch.PREF_INVALID:
                    default:
                        lucidapi.warn("Unknown pref type: %d", btype);
                        break;
                }
            }
            catch (e)
            {
                lucidapi.error(e);
                throw e;
            }
        },

        setLucidPref: function( key, val )
        {
            var prefs = this.getLucidPrefBranch();
            var btype = prefs.getPrefType( key );
            switch (btype) {
                case Ci.nsIPrefBranch.PREF_STRING:
                    lucidapi.assert(typeof(val) == "string");
                    return prefs.setCharPref(key, val);
                    break;
                case Ci.nsIPrefBranch.PREF_INT:
                    lucidapi.assert(typeof(val) == "number");
                    return prefs.setIntPref(key, val);
                    break;
                case Ci.nsIPrefBranch.PREF_BOOL:
                    lucidapi.assert(typeof(val) == "boolean");
                    return prefs.setBoolPref(key, val);
                    break;
                case Ci.nsIPrefBranch.PREF_INVALID:
                default:
                    lucidapi.warn("Unknown pref type: %d", btype);
                    break;
            }
        }
    };

    lucidapi.config.__defineGetter__("default_cache_keypass", function() { return this.getLucidPref("default_cache_keypass"); } );
    lucidapi.config.__defineGetter__("hide_expired", function() { return this.getLucidPref("hide_expired"); } );
    lucidapi.config.__defineGetter__("noInPlaceResults", function() { return this.getLucidPref("noInPlaceResults"); } );
    lucidapi.config.__defineGetter__("google_mail_autopop_sigkey", function() { return this.getLucidPref("google.mail.autopop.sigkey"); } );
    lucidapi.config.__defineGetter__("google_mail_autopop_enckey", function() { return this.getLucidPref("google.mail.autopop.enckey"); } );
    lucidapi.config.__defineGetter__("google_mail_enabled", function() { return this.getLucidPref("google.mail.enabled"); } );
    lucidapi.config.__defineGetter__("google_mail_encryptbutton", function() { return this.getLucidPref("google.mail.encryptbutton"); } );
    lucidapi.config.__defineGetter__("default_private_key", function() { return this.getLucidPref("default_private_key"); } );
    lucidapi.config.__defineGetter__("lastVersion", function() { return this.getLucidPref("lastVersion"); } );
    lucidapi.config.__defineSetter__("lastVersion", function(val) { this.setLucidPref("lastVersion",val); } );
    lucidapi.config.__defineGetter__("default_signing_mode", function() { return this.getLucidPref("default_signing_mode"); } );
    lucidapi.config.__defineGetter__("enable_keysync", function() { return this.getLucidPref("enable_keysync"); } );
    lucidapi.config.__defineGetter__("key_src_url", function() { return this.getLucidPref("key_src_url"); } );
    lucidapi.config.__defineGetter__("google_login", function() { return this.getLucidPref("google_login"); } );
    lucidapi.config.__defineGetter__("readonly_keyrings", function() { return this.getLucidPref("readonly_keyrings"); } );
    lucidapi.config.__defineSetter__("readonly_keyrings", function(val) { this.setLucidPref("readonly_keyrings",val); } );
    lucidapi.config.__defineGetter__("default_textmode_encrypt", function() { return this.getLucidPref("default_textmode_encrypt"); } );
    lucidapi.config.__defineGetter__("always_show_save", function() { return this.getLucidPref("always_show_save"); } );
    lucidapi.config.__defineGetter__("logtofile", function() { return this.getLucidPref("logtofile"); } );
    lucidapi.config.__defineGetter__("loglevel", function() { return this.getLucidPref("loglevel"); } );

    lucidapi.config.__defineGetter__("keystore_path", function() {
        var retval = this.getLucidPref("keystore_path");
        if (!retval || retval.length < 1)
        {
            const dirsvc = lucidapi.getXpcomService("@mozilla.org/file/directory_service;1","nsIProperties");
            var dirprop = (IS_WINDOWS ? "AppData" : "Home");
            var dirfile = new NSILocalFile( dirsvc.get(dirprop, Ci.nsIFile).path );
            dirfile.appendRelativePath((IS_WINDOWS ? "" : ".") + "gnupg");
            retval = dirfile.path;
        }
        return retval;
    } );

    lucidapi.config.__defineGetter__("logfile_path", function() {
        var retval = null;
        if (!retval || retval.length < 1)
        {
            const dirsvc = lucidapi.getXpcomService("@mozilla.org/file/directory_service;1","nsIProperties");
            var dirfile = new NSILocalFile( dirsvc.get("ProfD", Ci.nsIFile).path );
            dirfile.appendRelativePath("lucidpg.log");
            retval = dirfile.path;
        }
        return retval;
    } );

    lucidint.denormalizeFileSeps = function( path )
    {
        if (IS_WINDOWS)
        {
            var re = new RegExp("/", "g");
            path = path.replace(re, "\\");
        }
        return path;
    }

    /* http://developer.mozilla.org/en/docs/Writing_textual_data */
    function saveFileData( fileobj, data, isbinary, append )
    {
        // use 0x02 | 0x10 to open file for appending.
        const WRITE_MODE = (append ? 0x10 : 0x20) | 0x02 | 0x08 ;
        const WRITE_PERMISSION = 0600;
        try
        {
            lucidapi.assert(typeof(data) == "string");
            var parentdir = fileobj.parent;
            if (!parentdir.exists())
                parentdir.create(Ci.nsIFile.DIRECTORY_TYPE,0700);
            var fstream = new NSIFileOutputStream();
            var ostream = null;
            fstream.init(fileobj, WRITE_MODE, WRITE_PERMISSION, 0);
            if (isbinary)
            {
                ostream = new NSIBinaryOutputStream();
                ostream.setOutputStream(fstream);
                ostream.writeBytes(data,data.length);
            }
            else
            {
                ostream = new NSIConverterOutputStream();
                ostream.init(fstream, "UTF-8", 0, 0x0000);
                ostream.writeString(data);
            }
        }
        finally
        {
            lucid_safeclose(ostream);
            lucid_safeclose(fstream);
        }
    }

    function lucid_bytes2string(bytes)
    {
        var cooked = [];
        for (var ii = 0; ii < bytes.length; ii++)
        {
            cooked.push(String.fromCharCode(bytes.charCodeAt(ii) & 0xff));
        }
        return cooked.join("");
    }

    /* http://developer.mozilla.org/en/docs/Reading_textual_data */
    function lucid_loadFileData( fileobj, isbinary )
    {
        var retval = "";
        try
        {
            var fstream = new NSIFileInputStream();
            var istream = null;
            lucidapi.assert(fileobj instanceof Ci.nsILocalFile);
            lucidapi.assert(fileobj.exists(), "File does not exist");
            fstream.init(fileobj, -1, -1, Ci.nsILocalFile.CLOSE_ON_EOF);
            if (isbinary)
            {
                istream = new NSIBinaryInputStream();
                istream.setInputStream(fstream);
                var bytes = istream.readBytes(istream.available());
                retval = lucid_bytes2string(bytes);
            }
            else
            {
                istream = new NSIConverterInputStream();
                istream.init(fstream, "UTF-8", 1024, 0xFFFD);
                var str = {};
                while (istream.readString(1024, str) != 0) {
                    retval += str.value;
                }
            }
        }
        finally
        {
            lucid_safeclose(istream);
            lucid_safeclose(fstream);
        }
        return retval;
    }

    sm_passCache.askForPassphrase = function( keyid )
    {
        var retval = sm_passCache.entries[keyid];

        if (retval)
            return retval;

        var password = {value: null};
        var check = {value: lucidapi.config.default_cache_keypass};
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");

        var title = lucidapi.i18n.getString("password.dialog.title");
        var question = lucidapi.i18n.getFormattedString("password.dialog.description",[keyid]);
        var checkques = lucidapi.i18n.getString("password.dialog.save-password-checkbox");

        if (prompts.promptPassword(null, title, question, password, checkques, check))
        {
            retval = password.value;
            if (check.value)
            {
                sm_passCache.entries[keyid] = retval;
//                var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
//                observerService.notifyObservers(null,"lucid-pcache","filled");
            }
        }
        return retval;
    };

    function getKeyRingFile( ringname )
    {
        var gnupghome = lucidapi.config.keystore_path;
        var nsifile = new NSILocalFile(gnupghome);
        nsifile.appendRelativePath(ringname);
        return nsifile;
    }

    function getKeyRingData( ringname )
    {
        var nsifile = getKeyRingFile(ringname);
        if (!nsifile.exists())
            return null;
        var bytes = lucid_loadFileData(nsifile,true);
        return bytes;
    }

    function lucid_saveKeyData(pubdata,secdata)
    {
        if (lucidapi.config.readonly_keyrings)
            throw lucidapi.createThrowable("luciderr.readOnlyKeyrings");
        if (pubdata)
        {
            var pubfile = getKeyRingFile("pubring.gpg");
            saveFileData(pubfile,pubdata,true);
        }
        if (secdata)
        {
            var secfile = getKeyRingFile("secring.gpg");
            saveFileData(secfile,secdata,true);
        }
    }

    function lucid_getKeyData(secret)
    {
        if (secret)
        {
            return getKeyRingData("secring.gpg");
        }
        else
        {
            return getKeyRingData("pubring.gpg");
        }
    }

    function lucid_getDualKeyData(options)
    {
        if (!options)
            options = {};
        options.secKeyringData = getKeyRingData("secring.gpg");
        options.pubKeyringData = getKeyRingData("pubring.gpg");
        return options;
    }

    lucidapi.hasArmorType = function(text,type)
    {
        if (!text)
            return false;
        var header = "-----BEGIN PGP " + type + "-----";
        return text.indexOf(header) > -1;
    }
    
    lucidapi.encryptImpl = function(data,options)
    {
        lucid_getDualKeyData(options);
        var pubKeyChainData = options.pubKeyringData;
        if (!pubKeyChainData || pubKeyChainData.length < 1)
        {
            throw lucidapi.createThrowable("luciderr.noKeychainData");
        }

        if (options.sigKeyIds)
        {
            options.passCache = sm_passCache;
        }
        return pgp_encryptData(data, options);
    }

    lucidapi.decryptImpl = function(data,options)
    {
        options = lucid_getDualKeyData(options);

        options.passCache = sm_passCache;
        var privKeyChainData = options.secKeyringData;
        if (!privKeyChainData || privKeyChainData.length < 1)
        {
            throw lucidapi.createThrowable("luciderr.noKeychainData");
        }
        var result = pgp_decryptData(data, options);
        return result;
    }

    lucidapi.hasKeyrings = function()
    {
        try
        {
            if (!lucid_getKeyData(true) && !lucid_getKeyData(false))
                return false;
        } catch(e) {
            lucidapi.error(e);
        }
        return true;
    }

    lucidapi.getKeyChain = function(secret)
    {
        try
        {
            var keydata = lucid_getKeyData(secret);
            if (!keydata || keydata.length < 1)
            {
//                throw lucidapi.createThrowable("luciderr.noKeychainData");
                return [];
            }
            return pgp_loadKeyChain(keydata);
        } catch(e) {
            lucidapi.error(e);
            return [];
        }
    }

    /*
     * Open the file selected and return the choosen file.
     * If no file is selected, null is returned.
     */
    lucidapi.openFileSelector = function( win, folders, initialfolder )
    {
        var mode = Ci.nsIFilePicker.modeOpen;
        if (folders)
        {
            mode = Ci.nsIFilePicker.modeGetFolder;
        }
        var fp = new NSIFilePicker();
        if (initialfolder)
            try
            {
                fp.displayDirectory = new NSILocalFile(initialfolder);
            }
            catch (e)
            {
                lucidapi.warn(e);
            }


//        var i18n = lucid_getNativeStringBundle();
        fp.init(win, lucidapi.i18n.getString('fileSelectorSelectFile'), mode);
        fp.appendFilters(Ci.nsIFilePicker.filterAll);
        return (fp.show() == Ci.nsIFilePicker.returnOK) ? fp.file.path : null;
    };

    lucidapi.saveThingWithPicker = function(win,text)
    {
        var fp = new NSIFilePicker();
        fp.init(win, null, Ci.nsIFilePicker.modeSave);
        fp.appendFilters(Ci.nsIFilePicker.filterText | Ci.nsIFilePicker.filterAll);
        var a = fp.show();
        if (a != Ci.nsIFilePicker.returnOK && a != Ci.nsIFilePicker.returnReplace)
            return false;

        var filePath = fp.file.path;
        //Need to remove the file before save
        var fileObj = new NSILocalFile(filePath);
        if (fileObj.exists())
            fileObj.remove(true);
        saveFileData(fileObj, text);
        return true;
    }

    lucidapi.generateKeys = function(userid,passphrase,keysize,subsize,expiresecs)
    {
        if (!keysize)
            throw lucidapi.createThrowable("luciderr.badKeySize", "unspecified");
        if (!subsize)
            throw lucidapi.createThrowable("luciderr.badKeySize", "unspecified");
        return pgp_generateKeys(userid,passphrase,keysize,subsize,expiresecs);
    }

    lucidapi.loadThingWithPicker = function(win)
    {
        var filePath = lucidapi.openFileSelector(win);
        if (filePath)
            return lucid_loadFileData(new NSILocalFile(filePath));
        return null;
    };

    lucidapi.clearPassphraseCache = function()
    {
        sm_passCache.entries = {};
//        var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
//        observerService.notifyObservers(null,"lucid-pcache","cleared");
    }
    
    lucidapi.openTab = function(url)
    {
        var wm = lucidapi.getXpcomService("@mozilla.org/appshell/window-mediator;1","nsIWindowMediator");
        var mainWindow = wm.getMostRecentWindow("navigator:browser");
        var browser = mainWindow.getBrowser();
        browser.selectedTab = browser.addTab(url);
    }

    lucidapi.openPrefsDialog = function()
    {
        var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
        /* var win = */
        ww.openWindow(null, "chrome://lucidity/content/options.xul", "lucidityPreferences", "chrome,centerscreen,toolbar", null);
    };

    lucidapi.openViewSource = function(url,lineno)
    {
        var array = Components.classes["@mozilla.org/array;1"]
                              .createInstance(Components.interfaces.nsIMutableArray);

        var str      = Components.classes["@mozilla.org/supports-string;1"].
                                  createInstance(Components.interfaces.nsISupportsString);
        str.data     = url;

        array.appendElement(str,false);
        if (lineno)
        {
            array.appendElement(null,false);
            array.appendElement(null,false);
            var linenostr      = Components.classes["@mozilla.org/supports-string;1"].
                                      createInstance(Components.interfaces.nsISupportsString);
            linenostr.data     = "" + lineno;
            array.appendElement(linenostr,false);
        }

        var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
        ww.openWindow(null, "chrome://global/content/viewSource.xul", null, null, array);
    };

    lucidapi.openKeygenWizard = function(showintro)
    {
        var args = Components.classes["@mozilla.org/array;1"].createInstance(Components.interfaces.nsIMutableArray);

        var nsShowintro = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant);
        nsShowintro.setAsBool(Boolean(showintro));
        args.appendElement(nsShowintro,false);

        var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
        var features = "chrome, dialog, resizable=yes";
        if (!showintro)
            features += ", modal";
        features += ", centerscreen";
        ww.openWindow(null, "chrome://lucidity/content/keygen.xul", '', features,args);
    }

    function dumpResponse(req,fileext)
    {
        if (req)
        {
            if (!fileext)
                fileext = ("responseXML" in req) ? "xml" : "txt";
            var tmpfile = new NSILocalFile("C:\\gapiresp." + fileext);
            saveFileData(tmpfile,req.responseText);
            var ios = lucidapi.getXpcomService("@mozilla.org/network/io-service;1","nsIIOService");
            var tmpurl = ios.newFileURI(tmpfile);
            lucidapi.openTab(tmpurl.spec);
        }
    }

    lucidapi.keyLookup = function(emailorkeyid,doget)
    {
//        lucidapi.debug("got here!");
        var op = doget ? "get" : "index";
        var urlstr = "http://pgp.mit.edu:11371/pks/lookup?op=" + op + "&exact=on&search=" + emailorkeyid;
        var req = new NSIXMLHttpRequest();
        req.open('GET', urlstr, false);
//        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//        req.setRequestHeader("Authorization", 'GoogleLogin auth="' + cached_authToken + '"');
        req.send(null);
        dumpResponse(req,"html");
//        lucidapi.debug("got here2!");
        return req;
    }

    function openEncryptDialog( win, params, results )
    {
        var dlg = win.openDialog('chrome://lucidity/content/encrypt.xul', '', 'chrome, dialog, resizable=yes, modal, centerscreen', params, results);
        dlg.focus();
    }

    function chooseEncryptionOptions( win, preselect, presig, forcebin )
    {
        if (!preselect)
            preselect = [""];

        var params = {
            preselect: preselect.join(" "),
            presig: presig,
            forcebin: forcebin 
        }

        var results = {}

        openEncryptDialog( win, params, results );
        if (params.accepted)
            return results;
        return null;

    };

    lucidapi.formatSignatureResultMessage = function(siginfo)
    {
        if (siginfo.verified)
        {
            return lucidapi.i18n.getFormattedString("validSignInCrypt", [siginfo.keyid])
        }
        else
        {
            return lucidapi.i18n.getFormattedString("invalidSignInCrypt", [siginfo.keyid])
        }
    }

    lucidapi.openResultWindow = function(win, text, siginfo)
    {
        var title = lucidapi.i18n.getString('showTextDefaultTitle'); // editorTitle
        var description = lucidapi.i18n.getString('showTextDefaultDescription'); // editorDescription

        if (text == undefined)
            text = "";
        if (siginfo == undefined)
            siginfo = null;

        var params = {text: text, title: title, description: description, siginfo: siginfo};

        win.openDialog('chrome://lucidity/content/result.xul', '', 'chrome, dialog, resizable=yes, centerscreen', params).focus();
    }

    lucidapi.checkForUpgrade = function()
    {
        var version = lucidapi.config.lastVersion;

        var versionAct = LUCID_VERSION;

        if (version != versionAct)
        {
            lucidapi.config.lastVersion = versionAct;
            var mode = version;
            if (version == "")
                mode = "New";

            var prefssvc = lucidapi.getXpcomService("@mozilla.org/preferences-service;1", "nsIPrefService");
            var rootpref = prefssvc.getBranch("");
            // browser.dom.window.dump.enabled
            // extensions.checkCompatibility = false
                // rootpref.setIntPref("dom.max_script_run_time", 120);

//            javascript.options.showInConsole = true; //. Logs errors in chrome files to the Error Console.
//            nglayout.debug.disable_xul_cache = true; //. Disables the XUL cache so that changes to windows and dialogs do not require a restart. This assumes you're using directories rather than JARs. Changes to XUL overlays will still require reloading of the document overlaid.
//            browser.dom.window.dump.enabled = true; //. Enables the use of the dump() statement to print to the standard console. See window.dump for more info. You can also use nsIConsoleService from privileged script.
//            javascript.options.strict = true; //. Enables strict JavaScript warnings in the Error Console. Note that since many people have this setting turned off when developing, you will see lots of warnings for problems with their code in addition to warnings for your own extension. You can filter those with Console2.
//            extensions.logging.enabled = true; //. This will send more detailed information about installation and update problems to the Error Console.

            if (rootpref.getIntPref("dom.max_chrome_script_run_time") < 240)
                rootpref.setIntPref("dom.max_chrome_script_run_time", 240);
            if (rootpref.getIntPref("dom.max_script_run_time") < 240)
                rootpref.setIntPref("dom.max_script_run_time", 240);
            rootpref.setBoolPref("javascript.options.showInConsole", true);
            if (!lucidapi.hasKeyrings())
                lucidapi.config.readonly_keyrings = false;
            lucidapi.openKeygenWizard(true);
        }
    }

    lucidapi.promptError = function(e)
    {
        try {
            lucidapi.error(e);
            var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
            var title = lucidapi.i18n.getString("error.title");
            prompts.alert(null, title, e.message);
        } catch (e2) {
            Components.utils.reportError(e2);
            if (e2.stack)
                Components.utils.reportError(e2.stack);
        }
    }

    lucidapi.promptAlert = function()
    {
        try {
            var msg = lucidapi.l10n.apply(null,arguments);
            var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
            var title = lucidapi.i18n.getString("alert.title");
            prompts.alert(null, title, msg);
        } catch (e2) {
            lucidapi.error(e2);
        }
    }

    lucidapi.promptToSave = function(format)
    {
        try {
            if (!format)
                return false;
            if (format != 'b' && !lucidapi.config.always_show_save)
                return false;
            var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
            var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_SAVE +
                        prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_CANCEL;
            var formatstr = lucidapi.i18n.getString(format == 't' ? "format.text" : "format.binary");
            var msg = lucidapi.i18n.getFormattedString("savetofile.message",[formatstr]);
            var title = lucidapi.i18n.getString("savetofile.title");
            return prompts.confirmEx(null, title, msg, flags, "", "", "", null, {}) < 1;

        } catch (e2) {
            lucidapi.error(e2);
        }
    }

    lucidapi.promptConfirm = function()
    {
        try {
            var msg = lucidapi.l10n.apply(null,arguments);
            var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
            var title = lucidapi.i18n.getString("confirm.title");
            return prompts.confirm(null, title, msg);
        } catch (e2) {
            lucidapi.error(e2);
        }
    }

    lucidapi.promptWarning = function()
    {
        try {
            var msg = lucidapi.l10n.apply(null,arguments);
            var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
            var title = lucidapi.i18n.getString("error.title");
            prompts.alert(null, title, msg);
        } catch (e2) {
            lucidapi.error(e2);
        }
    }

    lucidapi.exportKeys = function(win,secret,keyid)
    {
        try {
            var keyChain = lucidapi.getKeyChain(secret);
            for (var ii = 0; ii < keyChain.length; ii++)
            {
                var theKey = keyChain[ii];
                if (theKey.getKeyId() == keyid)
                {
                    var data = theKey.serialize();
                    lucidapi.openResultWindow(win,pgp_armorWrap(data, secret ? "PRIVATE KEY BLOCK" : "PUBLIC KEY BLOCK"));
                    break;
                }
            }
        } catch (e2) {
            lucidapi.error(e2);
        }
    }

    lucidapi.CryptoOperator = lucidapi.AbstractClass.extend(function(classref) { return {

        initialize: function(win)
        {
            classref.superClass.initialize.call(this);
            this.m_win = win;
        },

        showEditorWindow: function( text, siginfo )
        {
            lucidapi.openResultWindow(this.m_win,text,siginfo);
        },

        getInvocationTarget: function()
        {
            var data = lucidapi.loadThingWithPicker(this.m_win);
            return data;
        },

        getInvocationTargetWrapper: function(invokeopts)
        {
            if (invokeopts && invokeopts.fromfile)
            {
                return lucidapi.loadThingWithPicker(this.m_win);
            }
            else
            {
                return this.getInvocationTarget();
            }
        },

        handleInplaceResult: function(resultobj)
        {
            return false;
        },

        handleSuccessfulCrypt: function(resultobj)
        {
            var stuff = resultobj.m_result;
            var sigdata = resultobj.m_siginfo;
//            if ((stuff) && resultobj.m_format == 'b')
            if ((stuff) && lucidapi.promptToSave(resultobj.m_format))
            {
                lucidapi.saveThingWithPicker(this.m_win, stuff);
            }
            else
            {
                if (!lucidapi.config.noInPlaceResults && (this.handleInplaceResult(resultobj)))
                    return;
                this.showEditorWindow( stuff, sigdata );
            }
        },

        handleError: function(e)
        {
            lucidapi.promptError(e);
        },

        invokeEncryption: function(invokeopts) {
            try {
                if (!invokeopts)
                    invokeopts = {};
                var text = this.getInvocationTargetWrapper(invokeopts);
                if (!text)
                {
                    lucidapi.promptWarning("luciderr.noData");
                    return;
                }

    //            var tryPosition = text.indexOf("-----BEGIN PGP SIGNED MESSAGE-----");
                var tryPosition = text.indexOf("-----BEGIN PGP MESSAGE-----");

                if (tryPosition != -1)
                {
                    if (!lucidapi.promptConfirm("alreadyCrypt"))
                        return;
                }

                var chosenopts = chooseEncryptionOptions(this.m_win,invokeopts.preselect,invokeopts.presig,invokeopts.fromfile);
                if (!chosenopts)
                {
                    return; // user canceled;
                }

                lucidapi.assert(typeof(chosenopts.encKeyIds) == "string");

                var result = lucidapi.encryptImpl(text,chosenopts);

                this.handleSuccessfulCrypt(result);
            } catch (e) {
                this.handleError( e );
            }
        },

        invokeDecryption: function(invokeopts) {
            try {
                if (!invokeopts)
                    invokeopts = {};
                var text = this.getInvocationTargetWrapper(invokeopts);
                if (!text) {
                    lucidapi.promptWarning("luciderr.noData");
                    return;
                }
                var options = {};
                var result = lucidapi.decryptImpl(text,options);
                this.handleSuccessfulCrypt(result);
            } catch (e) {
                this.handleError(e);
            }
        },

        invokeVerification: function(invokeopts) {
            try {
                if (!invokeopts)
                    invokeopts = {};
                var text = this.getInvocationTargetWrapper(invokeopts);
                if (!text) {
                    lucidapi.promptWarning("luciderr.noData");
                    return;
                }
                var options = {
                    verifyOnly: true
                }
                var result = lucidapi.decryptImpl(text,options);
                this.handleSuccessfulCrypt(result);
            } catch (e) {
                this.handleError(e);
            }
        },

        invokeImport: function(invokeopts) {
            try {
                if (!invokeopts)
                    invokeopts = {};
                var text = this.getInvocationTargetWrapper(invokeopts);
                if (!text) {
                    lucidapi.promptWarning("luciderr.noData");
                    return;
                }

                var options = lucid_getDualKeyData();
                var result = pgp_importKeys(text, options);
                if (invokeopts.noconfirm || lucidapi.promptConfirm("confirm.import"))
                {
                    lucid_saveKeyData(result.newPubData,result.newSecData);
                    return true;
                }
            } catch (e) {
                this.handleError(e);
            }
            return false;
        }
    }});

    var logfileReentranceFlag = false;
    function logEntryToFile(logmsg)
    {
        try
        {
            if (logfileReentranceFlag)
                return
            logfileReentranceFlag = true;
            if (logmsg.level > lucidapi.config.loglevel)
                return;
            var msg = logmsg.message;
            var stack = logmsg.stack;
            var text = lucidapi.sprintf( "%d %d %s\n", (new Date()).getTime(), logmsg.level, msg );

            while (stack)
            {
                text += "\t" + stack.filename + ":" + stack.lineNumber + "\n";
                stack = stack.caller;
            }
            saveFileData( new NSILocalFile(lucidapi.config.logfile_path), text, false, true );
        }
        catch (e)
        {
            // todo find a way to log this without reentrance
        }
        finally
        {
            logfileReentranceFlag = false;
        }
    }

    var SystemObserver = lucidapi.AbstractClass.extend(function(classref) {

        var skip = 1;

        function entropyCallback(event) {
            try {
                switch (event.type) {
                    case "keypress":
                        pgp_updateEntropy(0);
                        break;
                    case "mousemove":
                        if (skip-- > 1)
                            break;
                        skip = (Math.round(Math.random() * 255) & 255);
                        pgp_updateEntropy((event.screenX << 16) + event.screenY);
                        break;
                }
            } catch (e) {
                lucidapi.error(e)
            }
        }

        return {

            initialize: function(win)
            {
                classref.superClass.initialize.call(this);

                // gotta directly hookup the first window, but the rest will
                // be caught by the #observe method
                this.hookupWindow(win);

                var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
                observerService.addObserver(this,"quit-application",false);
                observerService.addObserver(this,"domwindowopened",false);
                observerService.addObserver(this,"domwindowclosed",false);
                observerService.addObserver(this,"lucid-logmsg",false);
            },

            register: function(win){
                this.hookupWindow(win);
            },

            observe: function(subject,topic,data){
                switch (topic) {
                    case "domwindowopened":
                        this.hookupWindow(subject);
                        break;
                    case "quit-application":
                        // todo shutdown and sync-related call here.
                        break;
                    case "lucid-logmsg":
                        if (lucidapi.config.logtofile)
                            logEntryToFile(JSON.fromString(data));
                        break;
                }
            },

            hookupWindow: function(win)
            {
                if (win instanceof Ci.nsIDOMJSWindow)
                {
                    // just in case the internal XPCOM bootstrap process changes
                    // and we might somehow be called twice for the same window.
                    win.removeEventListener("keypress", entropyCallback, false);
                    win.removeEventListener("mousemove", entropyCallback, false);

                    // Now register our entropy gatherer on this window.
                    win.addEventListener("keypress", entropyCallback, false);
                    win.addEventListener("mousemove", entropyCallback, false);
                }
            }

    }});

    var sysObserver = null;

    lucidapi.handleLoad = function(win)
    {
        // one-time initialization goes here.
        // will be currently be called by all browser.xul windows onLoad, but
        // all the ones subsequent to the first will be picked up by the
        // SystemObserver itself.

        if (sysObserver)
            return;

        sysObserver = new SystemObserver(win);
    }

})();
